Add VoxCPM v1 — lightweight VoxCPM TTS support (0.5B) - #256
Conversation
…d weight handling and embedding transpose
Bug: VoxCPM1 model produced pure noise ("elloそ。") instead of speech due to:
1. Synthesized `fusion_concat_proj` weight (Xavier init) treated as learned weight → wrong concat+linear fusion
2. Embedding weight transposed in V1 GGUF: `token_embd.weight` stored as [hidden, vocab] but audio.cpp expects [vocab, hidden]
Fix:
- Add `is_synthesized()` to TensorSource interface to distinguish loaded vs synthesized weights- Implement in TransformingTensorSource for V1 models- Add embedding weight transpose in set_backend_tensor() for `base_lm.embed_tokens.weight`
- Update 5 `has_fusion_proj` checks to exclude synthesized weights
- Test: "This is a test run for the fix" now transcribes as "This is a test." (was pure noise) --> but still wrong.
## Summary Fixed VoxCPM1 TTS producing pure noise by correcting tensor synthesis and shape validation issues. ## Changes - **`src/models/voxcpm2/assets.cpp`**: Only synthesize tensors missing from GGUF (not unconditionally). Fixed `feat_encoder.special_token` shape (1D vs 4D). Added relaxed rank handling in `set_backend_tensor()` for V1. - **`src/framework/assets/tensor_source.cpp`**: Added `relaxed_rank` parameter to `validate_expected_shape()` allowing shape mismatches when element counts match. ## Root Cause Synthesized (Xavier-initialized) tensors were used instead of learned checkpoint weights. The `is_synthesized()` check now correctly distinguishes true synthesized tensors (only `fusion_concat_proj` for V1) from loaded weights. ## Validation - VoxCPM1: 16kHz speech, RMS ~0.10-0.15 ✅ - VoxCPM2: 48kHz speech (no regression) ✅ - Embedding transpose: `[1024,73448]` → `[73448,1024]` ✅ - `has_fusion_proj=false` for V1 ✅
## Fix - Added GGUF metadata reading to `TensorSource` (tokenizer.ggml.*, voxcpm_*) - Created `VoxCPM1GgufTokenizer` + `load_voxcpm1_config_from_gguf()` for native GGUF loading - Added `VoxCPM2TokenizerWrapper` for dual JSON/GGUF tokenizer support - Updated `load_voxcpm2_assets()` to auto-detect/use GGUF metadata - Removed external JSON deps from `model_specs/voxcpm1.json` ## Test (ASR: sensevoice@11533) - VoxCPM1 0.5B: "This is a test run for the fix." ❌ (too fask) - VoxCPM1.5 1.5B: "I the touch for the." ❌ (too slow) ## Remaining Bugs 1. VoxCPM1 too fast (1.28s vs 2.5s) - early stop token 2. VoxCPM1.5 too slow (5.29s vs 2.5s) - arch diff
- config_gguf.cpp: output_sample_rate now falls back to sample_rate (not 16000) VoxCPM1.5 GGUF has sample_rate=44100 but no out_sample_rate → was defaulting to 16kHz - session.cpp: add V1-specific default min_tokens to prevent early stop token trigger VoxCPM1 (patch_size=2): min_tokens=20, VoxCPM1.5 (patch_size=4): min_tokens=12 Without this, stop token triggers at ~2 tokens causing 1.28s cutoff - Stop predictor weights correctly loaded via V1 relaxed rank (no transpose needed) GGUF stores [1024,2] (GGML), expected logical [2,1024] → to_ggml_dims → [1024,2] ✓ Results: VoxCPM1 (0.5B): durations scale 1.76s→4.32s with text length VoxCPM1.5 (1.5B): durations scale 2.56s→5.12s, correct 44.1kHz sample rate VoxCPM2: regression passes (48kHz, 1.28s) Files: config_gguf.cpp (+6), session.cpp (+14)
…VoxCPM2)**
VoxCPM1 (0.5B/1.5B) models now support voice cloning (`--voice-ref`) and streaming output (`--mode streaming`), matching the VoxCPM2 feature surface. The inference math was already shared; this unblocks the capability/option/reporting layer.
**Root causes fixed (5 gaps):**
- Capability advertisement: now exposes `Tts + {Offline, Streaming}` for V1 (was TTS-only)
- Family identity: `family_impl()` returns `"voxcpm1"` for V1 models (was hardcoded `"voxcpm2"`)
- Session options: `normalize_v1_session_options()` rewrites `voxcpm1.*` → `voxcpm2.*` keys so aliases work
- Request options: added `voxcpm1.*` aliases for all params (`prompt_text`, `min_tokens`, `guidance_scale`, `retry_badcase`, etc.)
- Model spec: `voxcpm1.json` adds `streaming` mode, correct sample rates (16kHz/44.1kHz)
**Changes:** 7 files, +167/−32 lines
- `src/models/voxcpm2/session.cpp` — option normalization, family-aware errors, request-option aliases
- `src/models/voxcpm2/loader.cpp` — capability advertisement, family-labeled errors
- `model_specs/voxcpm1.json` — streaming mode, tags, corrected description
- `docs/tts.md` — V1 streaming/voice-clone examples, `retry_badcase=false` requirement
- `tools/audiocpp_cli/audiocpp_cli_path_cases.json` — 3 new V1 path tests
- `webui/configs/models_catalog.json` + `model_params.json` — V1 WebUI entries
**Verified (CPU):**
| Test | Result |
|------|--------|
| V1 offline TTS | `family=voxcpm1` ✓ |
| V1 voice clone | 16kHz, 5.12s, RMS 0.115 ✓ |
| V1 streaming | 40×1280 chunks, 16kHz ✓ |
| V1 `voxcpm1.*` session/request options | accepted & applied ✓ |
| V1 capability inspection | `modes=offline,streaming` ✓ |
| V2 regression (offline/streaming) | 48kHz, parity maintained ✓ |
Streaming requires `retry_badcase=false` (same as V2, pre-existing design). No V2 behavior changes.
**Issue**: The audio quality is still bad
VoxCPM1 attention used identity longrope factors and a padded stop-token floor. The GGUF's real F32 factor arrays are now read and applied (prefill, stop behavior and duration match the VoxCPM.cpp reference), and the V1 default `min_tokens` is lowered to the reference floor so short utterances are no longer padded with trailing silence.
**Root causes fixed (2):**
- RoPE longrope factors were hardcoded to `1.0f` in the GGUF config path ("GGUF doesn't have native float arrays" was wrong — `GgufTensorSource` already parses them); every attention computation across all four transformers (base LM, residual LM, local encoder, local DiT) used identity positional encodings
- V1 default `min_tokens=20` (per patch_size) vs reference `kMinLen=2` — forced ~1.6s+ of audio and padded short utterances with trailing silence after the stop predictor fired
**Changes:** 2 files, +29/−12 lines
- `src/models/voxcpm2/config_gguf.cpp` — read `voxcpm_lm_config_rope_scaling_{short,long}_factor` f32 arrays via `optional_f32_array()` with size validation (`head_dim/2`), identity fallback only when the keys are absent
- `src/models/voxcpm2/session.cpp` — V1 default `min_tokens = 2` (≡ reference `step > kMinLen`), keeping the `--request-option min_tokens` override
**Verified (CPU, against reference `/workspace/pi/VoxCPM.cpp`):**
| Test | Result |
|------|--------|
| Prefill lm_hidden | l2 within ~2% of reference (was diverged) |
| Stop predictor ("This is a test run for the fix") | fires at pos=19 (was: never fired) |
| Duration | 1.60s (ref 1.68s), trailing silence 0.13s (ref 0.44s) |
| V2 regression | 48kHz output maintained ✓ |
| Embedding + fusion | `[73448,1024]` transpose intact, `has_fusion_proj=false` ✓ |
**Issue**: Voice clone is still not supported — `--task clon` is rejected and passing reference audio + text (`--task tts --voice-ref <wav>`) generates noise rather than cloned speech. Needs a port-audit of the VoxCPM1 reference-audio conditioning path. Full evidence in `docs/reports/2026-08-18_1128_VoxCPM1_RoPE_Longrope_Factors_Stop_Floor_Fix.md`.
…den impl Restore working voice cloning by fixing the reference-audio conditioning and AudioVAE encoder alignment against the golden VoxCPM.cpp port: - generator: only set the CFM `prefix_cond` from prefill rows carrying audio (audio_mask). Previously the trailing text row's zero feature overwrote the reference patch, feeding the DiT a zero acoustic anchor for voice cloning (matches torch feat[:, -1] semantics) - audiovae: re-enable VAD silence trimming for prompt/reference audio (matches golden server_common.cpp:842/878), then pad to patch alignment before VAE encoding (left for prompt, right for reference) - audiovae: drop the `stride % 2` output_padding on the encoder downsample conv so causal padding matches the reference encoder - assets: declare base_lm.embed_tokens.weight as [vocab, hidden] so V1 GGUFs storing the embedding transposed ([hidden, vocab]) load correctly - audiovae: add VOXCPM_DUMP_REF_MONO / REF_FEAT / ENC_STAGE debug dumps Validation (sensevoice-small STT, continuation-mode clone with the Anna reference): 6/6 target sentences transcribe exactly; text-only TTS unchanged. Reference-only cloning (ref_start/ref_end tokens) still fails identically in the golden VoxCPM.cpp - a model-level limitation.
VoxCPM1 voice cloning via `--voice-ref <wav>` produced non-cloned
speech, while `--audio <wav>` (plus `--reference-text`) cloned
correctly. Both flags carried the same user intent, but the CLI mapped
them to different request fields that the session treated as two
distinct audio roles.
**Root cause:** `--voice-ref` set `request.voice->speaker->audio`, which
the session consumed as *reference audio*. For VoxCPM1 the reference
path is wrong in two ways:
- `encode_prompt_audio()` only copies `prompt_text` inside the
`prompt_audio` branch, so a reference-only request dropped the
reference transcript entirely (the LM never saw it).
- The reference role right-pads the audio and prepends it wrapped in
the `<audio_prompt_start/end>` tokens 103/104. Those belong to
VoxCPM2's "reference-mode plumbing"; the V1 LM was only trained for
prompt-continuation cloning (golden VoxCPM.cpp uses
`--prompt-audio` + `--prompt-text`, and its V1 server never calls
`encode_reference_audio`).
**Fix:** in `VoxCPM2SessionBase::encoded_prompt_for_request()`, when the
model is V1 and only a reference audio is supplied (no `--audio`),
route it through the prompt path — the audio becomes `prompt_audio`
(left-padded, after `<audio_start>`) and `--reference-text` becomes
`prompt_text` (concatenated with the target text). V2 keeps the
reference-mode path untouched. Applies to both offline and streaming
runs (single shared function). Without `--reference-text` the request
now fails with the golden's exact rule ("prompt audio requires
prompt_text or reference_text").
**Changes:** 1 file, +24/−12 lines
- `src/models/voxcpm2/session.cpp` — V1 reference→prompt routing with
cache key/lookup/encode all using the effective audio roles
**Verified (CPU, 0.5B Q8_0):**
| Test | Result |
|------|--------|
| V1 `--voice-ref` + ref-text | byte-identical WAV to `--audio` + ref-text (same clone) |
| V1 `--voice-ref` without ref-text | clean error (matches golden iff rule) |
| V1 `--audio` regression | byte-identical output |
| V2 `--voice-ref` regression | 48kHz, byte-identical to pre-fix (reference mode preserved) |
| 5-voice clone batch (ana/eric/andrew/jenny/nicole) | 16kHz speech, RMS 0.06–0.08 ✓ |
**Note:** V1.5 (44.1kHz) fails at load with "encoder sample capacity
must be divisible by encoder stride" — pre-existing config gap (stride
1764 ∤ default capacity 240000), identical on `--audio` before this fix.
…main For fixing VocCPM v1 --voice-ref clone issue
Move VoxCPM GGUF tokenizer/config metadata reading out of the framework TensorSource interface into a new voxcpm2 GgufMetadataReader. Revert the validate_expected_shape relaxed_rank parameter and redundant <memory> include; tensor_source.h/.cpp now differ from upstream/main by a single line (is_synthesized).
Only the cloned voice is cached across requests; prompt-prefill and AudioVAE encoder/decoder graphs are freed at request end and rebuilt fresh on the next request. Idle VRAM drops to ~1.4GB after generation; very long text may require up to ~3.5GB VRAM during generation.
|
Removed any changes to the framework. |
# Conflicts: # webui/native/dist/index.html
# Conflicts: # webui/native/dist/index.html
|
I noticed performance down after merged v0.7. Small models like Omnivoice and VoxCPM v1 RTF 0.3x --> 0.6x. |
|
Known issue: the streaming voice is poor. Maybe that's the reason it was disabled by default. |
|
@jasonchen31 Just a quick comment: The current impl breaks the ownership and boundary of the components. GGUF/package differences should be handled at conversion, not by scattering v1 branches and tensor adaptation throughout the voxcpm2 runtime. You can write your own conversion script if the current C++ GGUF tool is not sufficient, as long as the script and exact conversion command are documented and reproducible. The model spec (and loader) should resolve the package into native voxcpm1 assets. In fact, if designed cleanly, loader.* can be safely removed by migrating the model to model spec v1, following the pattern used by other spec-v1 models. Overall, runtime code should then implement the actual v1 graph directly, without GGUF-specific tensor remapping. A cleaner pathway is to write a dedicated voxcpm1 model implementation with its own assets/config/loader/runtime, since voxcpm1 appears to involve more than config changes and minor graph edits? Let's figure out a the best way to support voxcpm1 together. |
|
@0xShug0 Thanks for your comments. Indeed, the AI tool directly merged v1 and v2 models and resulted in remapping, which is way more complicated and less efficient. As this is my first time play around with ggml and tts implementation, this is a precious learning experience for me.
I am now more into the new Audit8_TTS. Still just studying the architecture.... |
@jasonchen31 Yes that sounds good to me. Thanks! If you're interested in Audio8, feel free to submit a draft PR early to avoid potential conflicts. It should be an easy port if using Fish Audio as the template. |
…oading Port VoxCPM1 (tokenizer-free 0.5B TTS) as a community model under community_models/voxcpm1, reusing the VoxCPM2 / Local-DiT / CFM stack. Fix V1 GGUF loading: - embed_tokens weight transpose to [vocab, hidden] for ggml get_rows - correct fusion-projection handling so true V1 models do not use the synthesized Xavier weight as a real fusion weight - distinguish synthesized vs loaded tensors (is_synthesized) Refactor shared voxcpm2 components accordingly. Updates CMake registration, model_specs/voxcpm1.json, webui catalog entries, and cli path-test cases. Verified via STT: VoxCPM1/VoxCPM2 TTS and voice-clone generate content-correct speech; VoxCPM2 retains 48kHz output.
# Conflicts: # webui/native/dist/index.html
|
@0xShug0 all done and merged. Please have a check. |
1. Overview
Adds support for the OpenBMB VoxCPM-0.5B lightweight TTS model to audio.cpp (ported from VoxCPM.cpp), reusing the existing and already-released
voxcpm2model tree:voxcpm-0.5b-q8_0-audiovae-f16.ggufArchitecture (0.5B): VAE encoder 128 / decoder 1536, encoder_rates
[2,5,8,8], decoder_rates[8,8,5,2], patch_size 2, residual_lm 6 layers, encoder/dit 4 layers, 16 kHz, max_len 4096.Since the v1 GGUF stores a different tensor convention than v2 (folded AudioVAE weights, no
weight_v/weight_gsplit, nosr_cond_modeltensors,voxcpmarchitecture name), the port wraps the v2 loader with a GGUF tensor-adaptation layer and addsconfig.v1-guarded branches in the generator, mirroring the reference implementation (VoxCPM.cpp).All work is on
main, 21 commits ahead ofupstream/main(merge-base4e973b1), consisting of 12 porting commits plus merges.tensor_source.hdiffers from upstream by a single line (is_synthesized) andtensor_source.cppis identical — the port is structured to be upstreamable.2. Porting activities
config.jsonfrom the GGUF metadata — the previously shipped sidecar was wrong on ~8 axes (patch, residual_lm/encoder/dit layer counts, VAE dims and rates, sample rate 44.1 kHz vs the actual 16 kHz, max_len).weight_v/weight_gdecomposition and nosr_cond_model.*tensors.neorder; the v1 GGUF carries noaudiocpp.tensor_shapesoverride metadata (v2 does), so the adapter must present shapes itself.load_vae_weightsloader works unchanged against folded v1 weights byte-for-byte.fusion_concat_proj) case: elementwise-add fusion inputs, elementwise-add dit-mu, and a real residual_lm autoregressive step.VoxCPM1-GGUF/model directory with a config regenerated from its own GGUF metadata + tokenizer sidecars, and updatedmodel_specs/voxcpm1.jsonpackage targets accordingly.is_synthesized(), embedding transpose[hidden, vocab]→[vocab, hidden], and tensor synthesis only for tensors actually missing from the GGUF — turning pure noise into intelligible speech.audiocpp.vocab_*/ config keys) with a GGUF-native tokenizer, removing external sidecar dependence.retry_badcase=false).TensorSourceintovoxcpm2GgufMetadataReader, revertedvalidate_expected_shaperelaxed-rank param. Framework net delta: +1 lineis_synthesized.mem_saver+ unconditional end-of-request release; only the cloned voice is cached across requests).3. Changes per file (full diff vs upstream/main
4e973b1)CMakeLists.txtaudiocpp_add_model(voxcpm1 ...)reusing the 7 voxcpm2 sources; registersengine::models::voxcpm2::make_voxcpm1_loader.include/engine/framework/assets/tensor_source.hvirtual bool is_synthesized(...) { return false; }— the only framework change that survives; needed to distinguish real vs fabricated weights at the abstractTensorSourcelevel.include/engine/models/voxcpm2/loader.hmake_voxcpm1_loader().include/engine/models/voxcpm2/assets.hVoxCPM2Config::v1 = false;load_voxcpm2_assets()now takesbool is_v1.src/models/voxcpm2/loader.cppVoxCPM1Loader(family"voxcpm1"),load_voxcpm1_model(),make_voxcpm1_loader(),metadata_v1/capabilities_v1/cli_v1. Tasks:ttswith{offline, streaming}modes,supports_speaker_reference = true. GGUF viaload_voxcpm2_assets(path, is_v1=true).src/models/voxcpm2/assets.cppTransformingTensorSourcev1 adapter (biggest chunk):• v1→v2 tensor-name rename map (
token_embd.weight→base_lm.embed_tokens.weight, ggufblk.N.*→base_lm.layers.N.*/feat_encoder.encoder.layers.*/feat_decoder.estimator.decoder.layers.*/residual_lm.layers.*,attn_norm→input_layernorm,ffn_norm→post_attention_layernorm,attn_*→self_attn.*_proj,ffn_*→mlp.*_proj,time_mlp.*,output_norm.weight→base_lm.norm.weight, projection/fsq/stop mappings)• Folded weight-norm synthesis: for every
audio_vae.*.weightconv,X.weight_v→ folded tensor data as-is,X.weight_g→ per-row L2 norms (identity fold, see §4)• Identity
decoder.sr_cond_model.{2..5}.scale_embed.weight(ones) /.bias_embed.weight(zeros) since v1 GGUF carries no SR-conditioning tensors• Synthesized missing v1 tensors, only when absent from the GGUF (
feat_encoder.scale_embed/bias_embed,feat_encoder.fc_logvar,feat_encoder.diag,feat_encoder.merge,token_embd.extra_bias,fusion_concat_proj.weight/bias,stop_proj.weight,stop_head.weight)•
is_synthesized()override (map membership onsynthesized_tensors_)• Rank-tolerant
require_f32(accept element-count-equal, shape-different fetches) + relaxed-rank VAE weight_v anchors• Embedding transpose in
set_backend_tensor(): V1 GGUF storestoken_embd.weightas[hidden, vocab]but gglm expects[vocab, hidden]; transpose applied when shapes match the swap•
has_tensor/require_metadata/require_tensor_datafolded + synthesized lookups• Anchor fix:
encoder.fc_mu.weight_vuses computed encoder-in (encoder_dim << #rates= 2048), notdecoder_dim(1536)src/models/voxcpm2/generator.cpphas_fusion_proj:tensor != nullptr && !is_synthesized(...)(5 call sites — build/run/generate paths); residual input =AddModuleinstead of concat+linear when false (matches referencebuild_residual_fusion_input)• Added
add_dit_mu()helper; v1mu= elementwise add ofcurrent_lm_dit_hidden + residual_dit_hidden(matches referencebuild_dit_mu,mu_dim = hidden·(fusion?2:1), v1 → hidden)• CFM
musize check is now v1-aware (hidden_dim * (v1 ? 1 : 2))• v1 decode loop runs
residual_lm_.run_step(next_projected.residual_input).hidden(earlierfsq_lm_dit_hiddenshortcut removed)src/models/voxcpm2/minicpm.cppresidual_input=AddModule(lm_hidden, masked_current)instead of concat+linear; residual_lm always runs. RoPE longrope factors loaded from GGUF config for v1 (prefill lm_hidden l2 within ~2% of reference).src/models/voxcpm2/minicpm_blocks.hsrc/models/voxcpm2/session.cppmin_tokensfloor (avoids premature stop at ~2 tokens); stop-progress handling; voice-clone:--voice-refrouted through the prompt path (reference audio + transcript as reference_text), reference-onlyref_start/ref_endbranch (fails identically in golden impl — model limitation); streaming support; per-request VRAM release (unconditional end-of-request release of prefill + decoder graphs;mem_saveradditionally releases all generator graphs; only the cloned voice is cached).src/models/voxcpm2/audiovae.cppstride % 2output_padding on downsample conv so causal padding matches the reference encoder;VOXCPM_DUMP_REF_MONO/REF_FEAT/ENC_STAGEdebug dumps;release_encoder_graph()so encoder VRAM frees right after encode.src/models/voxcpm2/config_gguf.cpp/.hvoxcpm.*keys: architecture, dims, layer counts, VAE dims/rates, max_len, RoPE factors, stop floor).src/models/voxcpm2/gguf_metadata.cpp/.hGgufMetadataReader: framework-independent GGUF metadata accessor so the frameworkTensorSourcestays upstream-shaped.src/models/voxcpm2/tokenizer_gguf.cpp/.haudiocpp.vocab_*metadata.src/models/voxcpm2/tokenizer_wrapper.hsrc/models/voxcpm2/tokenizer_text.cpp/.htokenizepath chosen by tokenizer type.include/engine/models/voxcpm2/audiovae.hrelease_encoder_graph().include/engine/models/voxcpm2/generator.hrelease_runtime_memory().include/engine/models/voxcpm2/minicpm.hrelease_runtime_memory()on prefill/text-embedding runtimes.model_specs/voxcpm1.jsonvoxcpm1_0.5b_q8_0→VoxCPM1-GGUF(default).tools/audiocpp_cli/audiocpp_cli_path_cases.jsonvoxcpm1_tts,voxcpm1_voice_clone,voxcpm1_streaming_tts.webui/configs/models_catalog.jsonvoxcpm1catalog entry.webui/configs/model_params.jsonnum_inference_steps,guidance_scale,min_tokens, ...).webui/native/dist/index.htmldocs/tts.mdmodels/VoxCPM1-GGUF/config.json4. Key design: the identity-fold adapter
The v1 GGUF (OpenBMB reference converter) stores AudioVAE conv weights already folded (
weight = weight_g · weight_v / ‖weight_v‖), with noweight_v/weight_gsplit, whileaudiovae.cpprequests the decomposed names directly viarequire_f32. The adapter solves this without touching the VAE loader:Because
fold_weight_normmultiplies rowd0byweight_g[d0] / ‖row d0‖ = 1, the loader output equals the GGUF data byte-for-byte — an exact identity, with no layout drift relative to the reference runtime's consumption of the same bytes.5. Usage
Build
Run — VoxCPM-0.5B (16 kHz output)
build/linux-cpu-release/bin/audiocpp_cli \ --task tts --family voxcpm1 \ --model models/VoxCPM1-GGUF/voxcpm-0.5b-q8_0-audiovae-f16.gguf \ --backend cpu --text "Hello from VoxCPM1." --out out.wavVoice clone
Streaming
build/linux-cpu-release/bin/audiocpp_cli \ --task tts --family voxcpm1 \ --model models/VoxCPM1-GGUF/voxcpm-0.5b-q8_0-audiovae-f16.gguf \ --backend cpu --mode streaming --text "Hello from VoxCPM1." \ --request-option retry_badcase=false --out out.wavOptions
--tasktts--familyvoxcpm1--backendcpu,cuda,vulkan,metal,hip,bestbest--modeoffline,streamingofflineretry_badcase=false.--voice-ref--reference-text.--max-tokens4096--num-inference-steps10--guidance-scale2.0--session-option voxcpm1.mem_saver=true|falsefalse--session-option voxcpm1.prompt_cache_slots=<n>1--text-chunk-modedefault,tag_aware,japanese,endlinetag_aware6. Validation performed
validate_weight_anchorsandload_vae_weights/load_model_weightson CPU backend.speech.audio.deltaSSE chunks flow at the model native 16 kHz; validated via the voxcpm1 streaming WebUI script (webui/voxcpm1_stream_webui.py).lm_hiddenl2 within ~2% ofVoxCPM.cpp; stop predictor fires at pos=19; duration 1.60 s vs reference 1.68 s.config.v1, v2 defaultfalse); VoxCPM2 still generates 48 kHz speech with byte-identical output versus pre-change baseline.7. Supported modes
retry_badcase=false(same as v2).ref_start/ref_end) cloning fails identically in the goldenVoxCPM.cpp— a model-level limitation.8. Fixed issues (was: "Known issue: noisy output")
The "pure noise" blocker from the initial port is resolved. Root causes found and fixed:
fusion_concat_projwas Xavier-synthesized on every load and treated as a learned tensor. Fixed by addingis_synthesized()to theTensorSourceinterface (tensor_source.h+1 line) and overriding it inTransformingTensorSource; the 5has_fusion_projguards now exclude synthesized weights (falsefor true V1 models).token_embd.weightas[hidden, vocab]=[1024, 73448]; audio.cpp/ggml needs[vocab, hidden]. Fixed with a transpose inset_backend_tensor().feat_encoder.special_tokenshape (1D vs 4D).min_tokensfloor; durations scale 1.76 s→4.32 s with text length.stride % 2output_padding so causal padding matches the reference encoder (clone conditioning).9. Remaining tasks
models_catalog.json,model_params.json,native/dist/index.html)voxcpm1_tts,voxcpm1_voice_clone,voxcpm1_streaming_tts)min_tokensfloor parity vs reference (prefill l2 ~2%)mem_saver(idle ~1.4 GB)docs/gguf.mdsupport-table entry for voxcpm1audio.cpp-gguf(0.5B package)